Salesforceカスタムアプリの自動E2Eテスト: GitHub ActionsとPlaywrightで実現する夜間テスト戦略
情報システム室の進地@日比谷です。
クラスメソッドでは自社の請求に関するシステムをSalesforceに実装した締処理プログラムを中心に構成しています。長年の保守、運用を経て、この締処理プログラムの技術的負債の側面が無視できない状態となったため、現在、リプレイス処理を進めています。
請求の金額という非常にクリティカルなデータを扱うシステムであるため、QCDで最優先は品質です。開発に伴い壊れた機能がないか、速やかに、また繰り返し検知できることが必須です。
そこで、Salesforceにカスタムアプリケーションとして実装されるこのシステムのE2Eテストを毎晩定時実行する、という要件をGitHub ActionsとPlaywrightを利用して実現しました。
テスト用初期データを配備したSandbox組織を作成する
ステージング用途のSandbox組織を1つ用意します。
この組織には、
- 開発中(=テスト対象)のカスタムアプリケーション
- 必要なテストデータ
- 必要なメタデータ
を作り込みます。
実際のE2Eテストでは、このSandbox組織を対象にはテストを実行しません
。この組織は、あくまでテスト実行する別のSandboxの複製元
として活用します。
こうすることで、開発メンバーはE2Eテストのことを勘案することなく、あくまでステージング環境としてこの組織を活用できます。
このステージング用Sandbox組織のエイリアスを、以下ではstaging
と設定したものとします。
ステージング用Sandbox組織からテスト対象Sandbox組織を作成する
同じ組織ライセンス(例えば、Developer組織同士など)であれば、新規Sandbox作成時にclone(複製元)
に既存Sandboxを指定することで、既存Sandboxのメタデータ、レコードをまるっと引き継ぐことができます
。
これを利用して、E2Eテスト対象のSandbox組織をsfコマンドで作成するには、次のようにします。作成するSandbox組織のエイリアスをe2e
、本番組織のエイリアスをproduction
としています。
$ JOB_ID=$(sf org sandbox create --name e2e --alias e2e --clone staging -o production --async --no-prompt --json | jq -r ".result.Id")
ここでは--async
オプションを指定することでSandbox組織の作成を非同期実行し、コマンドを即座に終了させています。jqを使ってこの処理のジョブIDを取得し、変数JOB_ID
に格納しています。
Sandbox組織の作成には大体数時間はかかります。そこで、Sandbox組織の作成状態を確認するには先に取得したJOB_IDを使って次のコマンドを実行します。
$ MESSAGE=$(sf org resume sandbox --job-id $JOB_ID -o production --json | jq -r '.message')
これで変数MESSAGE
に完了
という文字列が入っていればSandbox組織の作成は完了しています。
本来的には jq -r '.status'
でステータスを見るのが正しそうですが、私の環境(MacOS + @salesforce/cli/2.65.8 darwin-x64 node-v20.5.0 )ではステータスの値は正しくSandbox組織の作成状況を示してはくれませんでした。
GitHub Actionsを活用して、毎晩テスト対象Sandboxに対してPlaywrightでE2Eテストを走らせる
GitHub Actionsを使ってPlaywrightのE2Eテストを定期実行する方法に関しては、
GitHub ActionsでPlaywright E2Eテストを定期実行し、結果をSlack通知する
を参照してください。
この記事では、E2Eテスト用のSandbox組織を作成し、そのSandbox組織に対してPlaywrightを走らせ、テスト後に不要なSandbox組織を削除する処理をGitHub Actionsで実現する方法を示します。
この要件を実現するには以下の制約を攻略する必要があります。
- Sandbox組織の作成には最低でも数時間かかるため、1つのワークフローではタイムアウトになる
- タイムアウトを回避するため、2つのワークフローで処理を構成するが、その場合、2つのワークフロー間で情報を受け渡す必要がある
これらを勘案したGitHub Actionsのワークフロー例を以下に示します。ワークフローは2つ作成します。
E2Eテスト実行環境のSandbox組織を作成するワークフロー
まず、E2Eテストを実行するSandbox毎日21時に作成するワークフローです。
.github/workflows/sandbox-creation.yml
として定義します。
name: Create Salsforce Sandbox for E2E Testing
on:
schedule:
- cron: '0 12 * * *' # 毎日21:00 JST (12:00 UTC)
env:
SALESFORCE_USERNAME_BASE: ${{ secrets.SALESFORCE_USERNAME_BASE }}
SALESFORCE_SECURITY_TOKEN: ''
SALESFORCE_CLONED_SANDBOX_NAME: ${{ secrets.SALESFORCE_CLONED_SANDBOX_NAME }}
SALESFORCE_CONSUMER_KEY: ${{ secrets.SALESFORCE_CONSUMER_KEY }}
jobs:
create-sandbox:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- name: Install Salesforce CLI
run: npm install @salesforce/cli --global
- name: Determine Sandbox to use
run: |
MONTH=$(date +%m)
DAY=$(date +%d)
SANDBOX_NUMBER=$((((MONTH * 31 + DAY) % 2) + 1))
SANDBOX_NAME="${SALESFORCE_CLONED_SANDBOX_NAME}cl${SANDBOX_NUMBER}"
echo "SANDBOX_NUMBER=$SANDBOX_NUMBER" > .env
echo "SANDBOX_NAME=${SANDBOX_NAME}" >> .env
echo "SALESFORCE_USERNAME=${SALESFORCE_USERNAME_BASE}.${SANDBOX_NAME}" >> .env
echo "SALESFORCE_SECURITY_TOKEN=$SALESFORCE_SECURITY_TOKEN" >> .env
echo "SALESFORCE_CLONED_SANDBOX_NAME=$SALESFORCE_CLONED_SANDBOX_NAME" >> .env
echo "SALESFORCE_LOGIN_URI=https://yourdomain--${SANDBOX_NAME}.sandbox.my.salesforce.com/" >> .env
echo "$MONTH $DAY" > date_info.txt
- name: Create Sandbox
run: |
source .env
echo "${{ secrets.SALESFORCE_JWT_SECRET_KEY }}" > e2etest.pem
sf org login jwt --client-id $SALESFORCE_CONSUMER_KEY --jwt-key-file e2etest.pem --username $SALESFORCE_USERNAME_BASE --instance-url https://yourdomain.my.salesforce.com --alias production
JOB_ID=$(sf org sandbox create --name $SANDBOX_NAME --alias $SANDBOX_NAME --clone $SALESFORCE_CLONED_SANDBOX_NAME -o production --async --no-prompt --json | jq -r ".result.Id")
echo "JOB_ID=$JOB_ID" >> .env
- name: Store Sandbox Info
uses: actions/upload-artifact@v4
with:
name: sandbox-info
include-hidden-files: true
path: |
.env
date_info.txt
- name: Clean up
run: rm e2etest.pem
sfコマンドを使用するにあたっては、最初に組織にログインをする必要があるため sf org sandbox create
する前に
sf org login jwt --client-id $SALESFORCE_CONSUMER_KEY --jwt-key-file e2etest.pem --username $SALESFORCE_USERNAME_BASE --instance-url https://yourdomain.my.salesforce.com --alias production
で本番組織にログインしています。ログインはOAuth 2.0 JWT Bearer Flowで行っています。こちらの詳しい方法(鍵の作り方やSalesforceでの設定方法)は
Lambda(Python)からSalesforce APIを自己証明書を使って安全にコールしてみた
の記事を参照してください。
また、日付の情報を.env
の情報を次のワークフローに引き継ぐために、artifactとしてアップロードしています。.envをartifactに含めるためにinclude-hidden-files: true
を指定していることに注意してください。
作成したSandbox組織でE2EテストをPlaywrightで実行するワークフロー
次に、実際にPlaywrightで作成したSandbox組織でE2Eテストを実行するワークフローです。
.github/workflows/exec-e2etest.yml
として定義します。
Sandbox組織作成を行うワークフローの6時間後(夜中の3時)に走らせます。
Sandbox組織の作成にかかる時間は数分から数日まで
幅があり、明示されないのですが、大体の場合、弊社の環境では数時間で完了しているので実運用上は6時間の間隔で問題ないと判断しました。
name: Execution E2E Test and Cleanup
on:
schedule:
- cron: '0 18 * * *' # 毎日3:00 JST (18:00 UTC)
env:
SALESFORCE_USERNAME_BASE: ${{ secrets.SALESFORCE_USERNAME_BASE }}
SALESFORCE_CONSUMER_KEY: ${{ secrets.SALESFORCE_CONSUMER_KEY }}
jobs:
e2e-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- name: Install Salesforce CLI
run: npm install @salesforce/cli --global
- name: Install Playwright
run: npx playwright install --with-deps
- name: Download Sandbox Info
uses: dawidd6/action-download-artifact@v6
with:
github_token: ${{ secrets.WORKFLOW_PAT }}
workflow: sandbox-creation.yml
name: sandbox-info
workflow_conclusion: success
- name: Determine Sandbox Names
run: |
source .env
read MONTH DAY < date_info.txt
SANDBOX_NUMBER=$((((MONTH * 31 + DAY) % 2) + 1))
OLD_SANDBOX_NUMBER=$((3 - SANDBOX_NUMBER))
OLD_SANDBOX_NAME="${SALESFORCE_CLONED_SANDBOX_NAME}cl${OLD_SANDBOX_NUMBER}"
echo "OLD_SANDBOX_NAME=$OLD_SANDBOX_NAME" >> .env
echo "SF_OLD_USERNAME=${SALESFORCE_USERNAME_BASE}.${OLD_SANDBOX_NAME}" >> .env
- name: Check Sandbox Status
run: |
source .env
echo "${{ secrets.SALESFORCE_JWT_SECRET_KEY }}" > e2etest.pem
sf org login jwt --client-id $SALESFORCE_CONSUMER_KEY --jwt-key-file e2etest.pem --username $SALESFORCE_USERNAME_BASE --instance-url https://yourdomain.my.salesforce.com --alias production
MESSAGE=$(sf org resume sandbox --job-id $JOB_ID -o production --json | jq -r '.message')
if [[ $MESSAGE == *"完了"* ]]; then
echo "SANDBOX_READY=true" >> .env
else
echo "SANDBOX_READY=false" >> .env
fi
- name: Run E2E Tests
run: |
source .env
if [ "$SANDBOX_READY" = "true" ]; then
npx playwright test
else
echo "Sandbox is not ready. Skipping E2E tests."
exit 1
fi
- name: Delete Old Sandbox
run: |
source .env
if [ "$SANDBOX_READY" = "true" ]; then
sf org delete sandbox --sandbox-name $OLD_SANDBOX_NAME --no-prompt
fi
- name: Clean up
run: rm e2etest.pem
sandbox-creation.yml
で作成、アップロードしたartifactをダウンロードします。
デフォルトではartifactはワークフロー間で共有できないので、dawidd6/action-download-artifact@v6
を使います。
プライベートリポジトリで共有するためにはgithub_token: ${{ secrets.WORKFLOW_PAT }}
の指定が必要です。WORKFLOW_PATにはrepo
とworkflow
のスコープを与えたGitHubのPersonal Access Tokenを作成して指定しています。
放っておくと毎晩テスト用のSandbox組織が作成されてしまうので、古いテスト用Sandboxは削除しています。
sf org delete sandbox --sandbox-name $OLD_SANDBOX_NAME --no-prompt
作成したばかりのSandbox組織は作成開始から24時間が経過するまで削除できないので、さらに1日前に作成したSandbox組織を削除する処理にしています。そのため、初回実行時は古いSandbox組織が存在しないため、この部分で必ずエラーになります。
まとめ
sfコマンドとSandbox組織、GitHub Actions、そしてPlaywrightを活用することで、毎晩のE2Eテストの定期実行を行う環境を構築できました。高い品質が求められるアプリケーションの開発においては、デイリーでE2Eテストが実行できることはとても心強い仕組みになります。
Salesforceに限らず、テスト環境を構築、そしてE2Eテストを実行というワークフローを実現する方法の参考になれば幸いです。